#include "expressionengine.h"

#include "../bindings/binding.h"
#include "../proxies/abstracttypeproxy.h"
#include "../types/colorset.h"
#include "../types/multiset.h"
#include "../types/timedmultiset.h"

#include <type.h>
#include <typefactory.h>

#include <QtCore/QMetaType>

using namespace core;
using namespace wfnet;

ExpressionEngine* expressionEngine = 0;

ExpressionEngine* ExpressionEngine::getInstance()
{
    if (expressionEngine == 0)
    {
        expressionEngine = new ExpressionEngine();
    }

    return expressionEngine;
}

void ExpressionEngine::destroy()
{
    delete expressionEngine;
    expressionEngine = 0;
}

void ExpressionEngine::registerTypeProxy(const TypeName& name,
    const TypeProxyName& proxyName)
{
    if (!m_registeredProxies.contains(name))
    {
        const int id = QMetaType::type(proxyName.toAscii().data());

        Q_ASSERT(id != 0);

        m_registeredProxies[name] = id;
    }
}

void ExpressionEngine::unregisterTypeProxy(const TypeName& name)
{
    if (m_registeredProxies.contains(name))
    {
        m_registeredProxies.remove(name);
    }
}

core::Type* ExpressionEngine::eval(const net::Expr& expr,
    const Binding& binding,
    const UserVariables& variables,
    const SystemVariables& systemVariables)
{
    Type* ret = 0;

    QScriptContext* context = m_engine.pushContext();

    bool shouldEval = true;

    foreach (const VariableName& var, variables.keys())
    {
        const TypeName colorType = variables[var];

        const ColorSet* color = dynamic_cast<const ColorSet* >(
            TypeFactory::getInstance()->getTypeInstance(colorType));

        Q_ASSERT(color != 0);

        const TypeName type = color->typeOf();

        Q_ASSERT(m_registeredProxies.contains(type));

        const int id =  m_registeredProxies[type];
        AbstractTypeProxy* proxy = static_cast<AbstractTypeProxy* >(
            QMetaType::construct(id));

        Q_ASSERT(proxy != 0);

        registerProxyInstance(proxy);
        const Type* trueType = TypeFactory::getInstance()->getTypeInstance(
            type);

        Q_ASSERT(trueType != 0);

        if (trueType->hasIdentity())
        {
            proxy->setIdentity(*trueType);
        }

        proxy->setEngine(&m_engine);

        if (binding.contains(var))
        {
            proxy->setValue(binding.getValue(var)->getValue());
        }
        else
        {
            shouldEval = false;
            break;
        }

        QScriptValue value = m_engine.newQObject(proxy);
        context->activationObject().setProperty(var, value);
    }

    if (shouldEval)
    {
        foreach (const VariableName& var, systemVariables.keys())
        {
            const SystemVariable variable = systemVariables[var];

            TypeFactory* factory = TypeFactory::getInstance();

            const Type* type = factory->getTypeInstance(
                variable.m_type);

            Q_ASSERT(type != 0);

            TypeName _type;

            bool isMultiset = false;

            if (type->matches(Multiset::NAME))
            {
                _type = Multiset::NAME;
                isMultiset = true;
            }
            else if (type->matches(TimedMultiset::NAME))
            {
                _type = TimedMultiset::NAME;
                isMultiset = true;
            }
            else
            {
                const ColorSet* color = dynamic_cast<const ColorSet* >(type);

                Q_ASSERT(color != 0);

               _type = color->typeOf();
            }

            Q_ASSERT(m_registeredProxies.contains(_type));

            const int id =  m_registeredProxies[_type];
            AbstractTypeProxy* proxy = static_cast<AbstractTypeProxy* >(
                QMetaType::construct(id));

            Q_ASSERT(proxy != 0);

            registerProxyInstance(proxy);
            const Type* trueType = isMultiset ? type :
                factory->getTypeInstance(_type);

            Q_ASSERT(trueType != 0);

            if (trueType->hasIdentity())
            {
                proxy->setIdentity(*trueType);
            }

            proxy->setEngine(&m_engine);

            if (!variable.m_containsVariable)
            {
                proxy->setValue(variable.m_value);
            }
            else
            {
                if (binding.contains(variable.m_variableName))
                {
                    TypeValue val = variable.m_value.toString();
                    val.replace(variable.m_variableName, binding.getValue(
                        variable.m_variableName)->getValue().toString());

                    proxy->setValue(val);
                }
                else
                {
                    shouldEval = false;
                    break;
                }
            }

            QScriptValue value = m_engine.newQObject(proxy);
            context->activationObject().setProperty(var, value);
        }
    }

    if (shouldEval)
    {
        QScriptValue value = m_engine.evaluate(expr);
        AbstractTypeProxy* _value = qobject_cast<AbstractTypeProxy* >(
            value.toQObject());

        ret = _value != 0 ? _value->getValue()->copy() : 0;
    }

    // cleanup
    foreach (const VariableName& var, variables.keys())
    {
        context->activationObject().setProperty(var, QScriptValue());
    }
    foreach (const VariableName& var, systemVariables.keys())
    {
        context->activationObject().setProperty(var, QScriptValue());
    }

    m_engine.popContext();

    foreach (AbstractTypeProxy* proxy, m_proxyInstancies)
    {
        delete proxy;
    }
    m_proxyInstancies.clear();

    return ret;
}

void ExpressionEngine::registerProxyInstance(AbstractTypeProxy* proxy)
{
    m_proxyInstancies.insert(proxy);
}

ExpressionEngine::ExpressionEngine() :
    m_registeredProxies(),
    m_proxyInstancies(),
    m_engine()
{

}

ExpressionEngine::~ExpressionEngine()
{

}
